package com.feifan.main.service;

import com.api.main.dto.RedPacketDto;
import com.api.main.model.RedDetail;
import com.api.main.model.RedRecord;
import com.api.main.model.RedRobRecord;
import com.api.main.service.IRedService;
import com.feifan.main.controller.RedController;
import com.feifan.main.mapper.RedDetailMapper;
import com.feifan.main.mapper.RedRecordMapper;
import com.feifan.main.mapper.RedRobRecordMapper;
import com.feifan.main.util.RedPacketUtil;
import com.sun.org.apache.regexp.internal.REDebugCompiler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.feifan.main.util.RedPacketUtil.divideRedPacket;

/**
 * @program: GetRegBag
 * @author: Xiaofan
 * @createTime: 2021-07-11 21:36
 * @description: Functions of this class is
 **/
@Service
public class RedServiceImpl implements IRedService {
    private static final Logger logger = LoggerFactory.getLogger(RedServiceImpl.class);
    //存储到redis缓存中时定义的key的前缀
    private static final String prefix = "redis:red:packet:";
    @Autowired
    private RedRecordMapper redRecordMapper;
    @Autowired
    private RedDetailMapper redDetailMapper;
    @Autowired
    private RedRobRecordMapper redRobRecordMapper;
    @Autowired
    private RedisTemplate<Object, Object> redisTemple;

    @Override
    public String handOut(RedPacketDto dto) {
        List<Integer> list = RedPacketUtil.divideRedPacket(dto.getAmount(), dto.getTotal());

        //生成唯一的全局标识符，返回给前端
        String redId = String.valueOf(System.nanoTime());//将纳秒作为标识符
        //将随机金额放入到缓存redis中
        redisTemple.opsForList().leftPushAll(redId, list.toArray());//必须要转成数组，

        //生成一个红包总金额的唯一标识符
        String redTotalKey = redId + ":total";
        //将红包总金额放入到redis缓存中
        redisTemple.opsForValue().set(redTotalKey, dto.getTotal());
        saveRed(dto, redId, list);//将随即金额的红包数据存入到数据库中

        return redId;
    }

    /**
     * @param userId 抢红包的人
     * @param redId  抢的哪个红包
     * @return 抢了多少钱
     */
    @Override
    public BigDecimal rob(String userId, String redId) {
        ValueOperations operations = redisTemple.opsForValue();
        Object o = operations.get(redId + userId + ":rob");
        if (o != null) {//表示抢过了，返回金额数
            return new BigDecimal(o.toString());
        }
        //表示没有抢过
        final String lockKey = redId + userId + "-lock";
        //调用setifAbsent方法，就是间接调用了分布式锁
        Boolean lock = operations.setIfAbsent(lockKey, redId);
        redisTemple.expire(lockKey, 24L, TimeUnit.HOURS);//设置一个过期时间
        if (click(redId)) //用来判断是否还有红包可以被抢
        {
            if (lock) {//上锁是为了一个人只能够抢一个红包，保持一对一关系
                Object money = redisTemple.opsForList().rightPop(redId);
                if (money != null) {
                    //如果抢到的金额不为空
                    ValueOperations operations1 = redisTemple.opsForValue();
                    //Object total=operations1.get(redId+":total");
                    operations1.decrement(redId + ":total");//在redis的底层单线程进行单次--
                    //若抢到了 就将其存入到数据库中
                    RedRobRecord redRobRecord = new RedRobRecord();
                    redRobRecord.setAmount(new BigDecimal(money.toString()));
                    redRobRecord.setRobTime(new Date());
                    redRobRecord.setUserId(Integer.parseInt(userId));
                    redRobRecord.setRedPacket(redId);

                    //将其存入到redis中
                    saveRobRed(redRobRecord);

                    //做一个24小时后就过期的设定
                    redisTemple.opsForValue().set(userId + "-" + redId + ":rob", money, 24L, TimeUnit.HOURS);//对抢到的那一笔金额在redis中进行设置
                    logger.info("当前用户抢到红包了:userId={} key={} 金额={}", userId, redId, money);//对日志进行记录
                    return new BigDecimal(money.toString());
                }
            }

        }
        return new BigDecimal(0L);//抢到的金额为0
    }

    private Boolean click(String redId) {
        ValueOperations operations = redisTemple.opsForValue();
        Object o = operations.get(redId + ":total");//把红包总额的金额数取出来
        //如果总金额还有，说明还可以抢红包
        return (o != null && new Integer(o.toString()) > 0);
    }

    @Async//代表异步进行
    @Transactional(rollbackFor = Exception.class)//代表当有异常的时候就自动回滚
    @Override
    public void saveRed(RedPacketDto dto, String redId, List<Integer> list) {

        RedRecord redRecord = new RedRecord();
        redRecord.setUserId(dto.getUserId());
        redRecord.setAmount(new BigDecimal(dto.getAmount()));
        redRecord.setTotal(dto.getTotal());
        redRecord.setCreateTime(new Date());
        redRecord.setRedPacket(redId);
        redRecordMapper.insertSelective(redRecord);


        //将随机金额放入到数据库中记录
        for (Integer money : list) {
            RedDetail redDetail = new RedDetail();
            redDetail.setAmount(new BigDecimal(money));
            redDetail.setRecordId(redRecord.getId());
            redDetail.setCreateTime(new Date());
            redDetailMapper.insertSelective(redDetail);
        }
    }

    @Async
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveRobRed(RedRobRecord redRobRecord) {
        redRobRecordMapper.insertSelective(redRobRecord);
    }
}